shortcuttrigger: Do elaborate matching for key events
authorMatthias Clasen <mclasen@redhat.com>
Sun, 22 Mar 2020 13:54:15 +0000 (09:54 -0400)
committerMatthias Clasen <mclasen@redhat.com>
Thu, 26 Mar 2020 03:14:45 +0000 (23:14 -0400)
Copy the logic from GtkKeyHash for matching key events
to shortcuts.

Adapt shortcuts test to work with the better matching,
by creating more complete key events.

gtk/gtkshortcuttrigger.c
testsuite/gtk/shortcuts.c

index 828a8486d2255a9d169035b28998e89793961c50..15ec8ebd914e15451f54ceab855580fbeea243d3 100644 (file)
@@ -40,6 +40,7 @@
 #include "gtkshortcuttrigger.h"
 
 #include "gtkaccelgroupprivate.h"
+#include "gtkprivate.h"
 
 typedef struct _GtkShortcutTriggerClass GtkShortcutTriggerClass;
 
@@ -499,25 +500,99 @@ gtk_keyval_trigger_trigger (GtkShortcutTrigger *trigger,
                             gboolean            enable_mnemonics)
 {
   GtkKeyvalTrigger *self = (GtkKeyvalTrigger *) trigger;
-  GdkModifierType modifiers;
+  guint keycode;
+  GdkModifierType state;
+  GdkModifierType mask;
+  int group;
+  GdkKeymap *keymap;
   guint keyval;
+  int effective_group;
+  int level;
+  GdkModifierType consumed_modifiers;
+  GdkModifierType shift_group_mask;
+  gboolean group_mod_is_accel_mod = FALSE;
+  const GdkModifierType xmods = GDK_MOD2_MASK|GDK_MOD3_MASK|GDK_MOD4_MASK|GDK_MOD5_MASK;
+  const GdkModifierType vmods = GDK_SUPER_MASK|GDK_HYPER_MASK|GDK_META_MASK;
+  GdkModifierType modifiers;
 
   if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
     return GTK_SHORTCUT_TRIGGER_MATCH_NONE;
 
-  /* XXX: This needs to deal with groups */
-  modifiers = gdk_event_get_modifier_state (event);
-  keyval = gdk_key_event_get_keyval (event);
-
-  if (keyval == GDK_KEY_ISO_Left_Tab)
-    keyval = GDK_KEY_Tab;
-  else
-    keyval = gdk_keyval_to_lower (keyval);
+  mask = gtk_accelerator_get_default_mod_mask ();
+
+  keycode = gdk_key_event_get_keycode (event);
+  state = gdk_event_get_modifier_state (event);
+  group = gdk_key_event_get_group (event);
+  keymap = gdk_display_get_keymap (gdk_event_get_display (event));
+
+  /* We don't want Caps_Lock to affect keybinding lookups.
+   */
+  state &= ~GDK_LOCK_MASK;
+
+  _gtk_translate_keyboard_accel_state (keymap,
+                                       keycode, state, mask, group,
+                                       &keyval,
+                                       &effective_group, &level,
+                                       &consumed_modifiers);
+
+  /* if the group-toggling modifier is part of the default accel mod
+   * mask, and it is active, disable it for matching
+   */
+  shift_group_mask = gdk_keymap_get_modifier_mask (keymap,
+                                                   GDK_MODIFIER_INTENT_SHIFT_GROUP);
+  if (mask & shift_group_mask)
+    group_mod_is_accel_mod = TRUE;
+
+  gdk_keymap_map_virtual_modifiers (keymap, &mask);
+  gdk_keymap_add_virtual_modifiers (keymap, &state);
+
+  modifiers = self->modifiers;
+  if (gdk_keymap_map_virtual_modifiers (keymap, &modifiers) &&
+      ((modifiers & ~consumed_modifiers & mask & ~vmods) == (state & ~consumed_modifiers & mask & ~vmods) ||
+       (modifiers & ~consumed_modifiers & mask & ~xmods) == (state & ~consumed_modifiers & mask & ~xmods)))
+    {
+      /* modifier match */
+      GdkKeymapKey *keys;
+      int n_keys;
+      int i;
+      guint key;
+
+      /* Shift gets consumed and applied for the event,
+       * so apply it to our keyval to match
+       */
+      key = self->keyval;
+      if (self->modifiers & GDK_SHIFT_MASK)
+        key = gdk_keyval_to_upper (key);
+
+      if (keyval == key && /* exact match */
+          (!group_mod_is_accel_mod ||
+           (state & shift_group_mask) == (self->modifiers & shift_group_mask)))
+        return GTK_SHORTCUT_TRIGGER_MATCH_EXACT;
+
+      gdk_keymap_get_entries_for_keyval (keymap,
+                                         self->keyval,
+                                         &keys, &n_keys);
+
+      for (i = 0; i < n_keys; i++)
+        {
+          if (keys[i].keycode == keycode &&
+              keys[i].level == level &&
+              /* Only match for group if it's an accel mod */
+              (!group_mod_is_accel_mod ||
+               keys[i].group == effective_group))
+            {
+              /* partial match */
+              g_free (keys);
+
+              return GTK_SHORTCUT_TRIGGER_MATCH_PARTIAL;
+            }
+        }
+      g_free (keys);
+    }
 
-  if (keyval != self->keyval || modifiers != self->modifiers)
-    return GTK_SHORTCUT_TRIGGER_MATCH_NONE;
 
-  return GTK_SHORTCUT_TRIGGER_MATCH_EXACT;
+  return GTK_SHORTCUT_TRIGGER_MATCH_NONE;
 }
 
 static guint
index e06b1814e9ffb74039375e02c26be9cf1629f194..ebf47ae21ddd4b10309bfa4d02307bd6d0e69f3b 100644 (file)
@@ -186,7 +186,7 @@ test_trigger_parse (void)
 static void
 test_trigger_trigger (void)
 {
-  GtkShortcutTrigger *trigger1, *trigger2, *trigger3, *trigger4;
+  GtkShortcutTrigger *trigger[4];
   GdkDisplay *display;
   GdkSurface *surface;
   GdkDevice *device;
@@ -204,13 +204,13 @@ test_trigger_trigger (void)
     { GDK_KEY_u, GDK_SHIFT_MASK,   FALSE, { GTK_SHORTCUT_TRIGGER_MATCH_NONE, GTK_SHORTCUT_TRIGGER_MATCH_NONE, GTK_SHORTCUT_TRIGGER_MATCH_NONE, GTK_SHORTCUT_TRIGGER_MATCH_NONE } }, 
     { GDK_KEY_u, GDK_SHIFT_MASK,   TRUE,  { GTK_SHORTCUT_TRIGGER_MATCH_NONE, GTK_SHORTCUT_TRIGGER_MATCH_NONE, GTK_SHORTCUT_TRIGGER_MATCH_EXACT, GTK_SHORTCUT_TRIGGER_MATCH_EXACT } }, 
   };
-  int i;
+  int i, j;
 
-  trigger1 = gtk_shortcut_trigger_ref (gtk_never_trigger_get ());
-  trigger2 = gtk_keyval_trigger_new (GDK_KEY_a, GDK_CONTROL_MASK);
-  trigger3 = gtk_mnemonic_trigger_new (GDK_KEY_u);
-  trigger4 = gtk_alternative_trigger_new (gtk_shortcut_trigger_ref (trigger2),
-                                          gtk_shortcut_trigger_ref (trigger3));
+  trigger[0] = gtk_shortcut_trigger_ref (gtk_never_trigger_get ());
+  trigger[1] = gtk_keyval_trigger_new (GDK_KEY_a, GDK_CONTROL_MASK);
+  trigger[2] = gtk_mnemonic_trigger_new (GDK_KEY_u);
+  trigger[3] = gtk_alternative_trigger_new (gtk_shortcut_trigger_ref (trigger[1]),
+                                            gtk_shortcut_trigger_ref (trigger[2]));
 
   display = gdk_display_get_default ();
   device = gdk_seat_get_keyboard (gdk_display_get_default_seat (display));
@@ -218,6 +218,13 @@ test_trigger_trigger (void)
 
   for (i = 0; i < G_N_ELEMENTS (tests); i++)
     {
+      GdkKeymapKey *keys;
+      int n_keys;
+
+      if (!gdk_keymap_get_entries_for_keyval (gdk_display_get_keymap (display),
+                                              tests[i].keyval, &keys, &n_keys))
+        continue;
+
       event = gdk_event_key_new (GDK_KEY_PRESS,
                                  surface,
                                  device,
@@ -225,24 +232,22 @@ test_trigger_trigger (void)
                                  GDK_CURRENT_TIME,
                                  tests[i].state,
                                  tests[i].keyval,
-                                 0,
-                                 0,
-                                 0,
+                                 keys[0].keycode,
+                                 keys[0].keycode,
+                                 keys[0].group,
                                  FALSE);
-      g_assert_cmpint (gtk_shortcut_trigger_trigger (trigger1, event, tests[i].mnemonic), ==, tests[i].result[0]);
-      g_assert_cmpint (gtk_shortcut_trigger_trigger (trigger2, event, tests[i].mnemonic), ==, tests[i].result[1]);
-      g_assert_cmpint (gtk_shortcut_trigger_trigger (trigger3, event, tests[i].mnemonic), ==, tests[i].result[2]);
-      g_assert_cmpint (gtk_shortcut_trigger_trigger (trigger4, event, tests[i].mnemonic), ==, tests[i].result[3]);
+      for (j = 0; j < 4; j++)
+        g_assert_cmpint (gtk_shortcut_trigger_trigger (trigger[j], event, tests[i].mnemonic), ==, tests[i].result[j]);
 
       gdk_event_unref (event);
     }
 
   g_object_unref (surface);
 
-  gtk_shortcut_trigger_unref (trigger1);
-  gtk_shortcut_trigger_unref (trigger2);
-  gtk_shortcut_trigger_unref (trigger3);
-  gtk_shortcut_trigger_unref (trigger4);
+  gtk_shortcut_trigger_unref (trigger[0]);
+  gtk_shortcut_trigger_unref (trigger[1]);
+  gtk_shortcut_trigger_unref (trigger[2]);
+  gtk_shortcut_trigger_unref (trigger[3]);
 }
 
 static int callback_count;